﻿using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Threading.Channels;
using AdvVerifyModels.SubstrateTracking;
using AdvVerifyModels.Models.Responses.SubstrateTracking;
using StackExchange.Redis;

namespace WaferServviceDemo
{
    public class ApplicationDbContext(DbContextOptions options) : DbContext(options)
    {
        public const string TransferId = "TransferId";

        public const string AlignerAngle = "AlignerAngle";

        public DbSet<KeyValue> KeyValues { get; set; }

        public DbSet<Substrate> Substrates { get; set; }
    }

    [Index(nameof(Key), IsUnique = true)]
    [Table("KeyValue")]
    public class KeyValue
    {
        public int Id { get; set; }

        public string Key { get; set; } = null!;

        public string Value { get; set; } = null!;
    }


    [Index(nameof(Foup), nameof(Slot), nameof(Location), IsUnique = true)]
    [Table("Substrate")]
    public class Substrate
    {
        public int Id { get; set; }

        public int Foup { get; set; }

        public int Slot { get; set; }

        public WaferLocation Location { get; set; }

        public WaferState State { get; set; }
    }


    public class SubstrateTrackingService
    {
        readonly IDbContextFactory<ApplicationDbContext> factory;
        static readonly JsonSerializerOptions EventSerializerOptions = new()
        {
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
        };
        const string LastEventId = "LastEventId";
        readonly ConnectionMultiplexer redis;

        readonly Channel<SubstrateTrackingEvent> channel = Channel.CreateBounded<SubstrateTrackingEvent>(10);

        public SubstrateTrackingService(
        ConnectionMultiplexer redis,
        IDbContextFactory<ApplicationDbContext> factory)
        {
            this.redis = redis;
            Task.Run(async () =>
            {
                await foreach (var evt in channel.Reader.ReadAllAsync())
                {
                    try
                    {
                        await HandleEvent(evt);
                    }
                    catch (Exception ex)
                    {

                    }
                }
            });
        }

        public async Task<SubstrateTrackingResult> Track()
        {
            var strayWafers = new List<StrayWafer>();
            var foups = new List<Foup>();
            strayWafers.Add(new StrayWafer
            {
                Location = AdvVerifyModels.SubstrateTracking.WaferLocation.LoadLock,
                Foup = 1,
                Slot = 1
            });
            return new SubstrateTrackingResult
            {
                LastEventId = "1",
                Foups = foups,
                StrayWafers = strayWafers
            };
        }
        public async Task SendEvent(SubstrateTrackingEvent evt)
        {
            await channel.Writer.WriteAsync(evt);
        }

        async Task HandleEvent(SubstrateTrackingEvent evt)
        {
            var db = redis.GetDatabase();
            var json = JsonSerializer.Serialize(evt, EventSerializerOptions);
            var id = (string?)await db.StreamAddAsync(Key.SubstrateTrackingStream, "event", json);

            using var ctx = factory.CreateDbContext();
            using var transaction = ctx.Database.BeginTransaction();
            await ctx.Database.ExecuteSqlAsync($"""
        INSERT INTO KeyValue(Key, Value) VALUES({LastEventId},{id!}) 
            ON CONFLICT(Key) DO UPDATE SET Value=excluded.Value
        """);

            switch (evt)
            {
                case FoupOpenEvent open:
                    //Foups[open.Foup - 1] = FoupState.Loaded;
                    //await ctx.Substrates
                    //    .Where(s => s.Foup == open.Foup && s.Location == WaferLocation.Foup)
                    //    .ExecuteDeleteAsync();
                    //var states = open.Slots;
                    //for (var i = 0; i < states.Length; i++)
                    //{
                    //    switch (open.Slots[i])
                    //    {
                    //        case WaferState.Present:
                    //        case WaferState.MappingError:
                    //            ctx.Substrates.Add(new Substrate
                    //            {
                    //                Foup = open.Foup,
                    //                Slot = i + 1,
                    //                Location = WaferLocation.Foup,
                    //                State = states[i]
                    //            });
                    //            break;
                    //    }
                    //}
                    //await ctx.SaveChangesAsync();
                    break;
                case FoupClosedEvent closed:
                    //Foups[closed.Foup - 1] = FoupState.Unloaded;
                    //await ctx.Substrates
                    //    .Where(s => s.Foup == closed.Foup && s.Location == WaferLocation.Foup)
                    //    .ExecuteDeleteAsync();
                    break;
                case WaferTransferEvent moved:
                    await ctx.Substrates
                        .Where(s => s.Foup == moved.Foup && s.Slot == moved.Slot)
                        .ExecuteUpdateAsync(setters => setters
                            .SetProperty(s => s.Location, moved.To));
                    break;
                case WaferStateChangedEvent changed:
                    await ctx.Substrates
                        .Where(s => s.Foup == changed.Foup && s.Slot == changed.Slot)
                        .ExecuteUpdateAsync(setters => setters
                            .SetProperty(s => s.State, changed.State));
                    break;
                case WaferSwapEvent swap:
                    await ctx.Substrates
                        .Where(s => s.Foup == swap.FoupIn && s.Slot == swap.SlotIn)
                        .ExecuteUpdateAsync(setters => setters
                            .SetProperty(s => s.Location, swap.LocationIn));
                    await ctx.Substrates
                        .Where(s => s.Foup == swap.FoupOut && s.Slot == swap.SlotOut)
                        .ExecuteUpdateAsync(setters => setters
                            .SetProperty(s => s.Location, swap.LocationOut));
                    break;
                case WaferTransferEndEvent:
                    db.StreamTrim(Key.SubstrateTrackingStream, 1000, true, CommandFlags.FireAndForget);
                    break;
                case ManuallyCreateWaferEvent wafer:
                    ctx.Substrates.Add(new Substrate
                    {
                        Foup = wafer.Foup,
                        Slot = wafer.Slot,
                        Location = WaferLocation.Foup,
                        State = WaferState.Present
                    });
                    await ctx.SaveChangesAsync();
                    break;
                case ManuallyDeleteWaferEvent wafer:
                    await ctx.Substrates
                        .Where(s => s.Foup == wafer.Foup && s.Slot == wafer.Slot)
                        .ExecuteDeleteAsync();
                    break;
                case ManuallySetLocationEvent wafer:
                    await ctx.Substrates
                        .Where(s => s.Foup == wafer.Foup && s.Slot == wafer.Slot)
                        .ExecuteUpdateAsync(setters => setters
                        .SetProperty(s => s.Location, wafer.IsDelete ? WaferLocation.Foup : wafer.Location));
                    break;
                case ManuallyModifyWaferEvent wafer:
                    await ctx.Substrates
                        .Where(s => s.Foup == wafer.OldFoup && s.Slot == wafer.OldSlot && s.Location == wafer.Location)
                        .ExecuteUpdateAsync(setters => setters
                        .SetProperty(s => s.Foup, wafer.NewFoup)
                        .SetProperty(s => s.Slot, wafer.NewSlot));
                    break;
            }

            await transaction.CommitAsync();
        }
    }
}
